# coding=utf8
from pyjvm.exceptions import StackOverflowError, OperandStackOverflow
from pyjvm.runtime.metadata import Method, Class, AccessFlags
from typing import TypeVar, Optional

SHIM_CLASS = Class()
SHIM_CLASS.name = '~shim'

RETURN_METHOD = Method()
RETURN_METHOD.access_flags = AccessFlags.ACC_STATIC
RETURN_METHOD.is_static = True
RETURN_METHOD.name = '<return>'
RETURN_METHOD.klass = SHIM_CLASS
RETURN_METHOD.code = bytes([0xb1])

ATHROW_METHOD = Method()
ATHROW_METHOD.access_flags = AccessFlags.ACC_STATIC
ATHROW_METHOD.is_static = True
ATHROW_METHOD.name = '<athrow>'
ATHROW_METHOD.klass = SHIM_CLASS
ATHROW_METHOD.code = bytes([0xbf])


class Slots(list):

    def __init__(self, size: int):
        list.__init__(self, [None] * size)


class OperandStack(list):

    def __init__(self, max_stack):
        list.__init__(self, [])
        self.__max_stack = max_stack

    def push(self, item):
        if len(self) <= self.__max_stack:
            return self.append(item)
        raise OperandStackOverflow()


ThreadFrame = TypeVar('ThreadFrame', bound='Frame')


class JvmStack(list):

    def __init__(self, max_size: int = 1024):
        self.__max_size = max_size
        list.__init__(self, [])

    def push(self, frame):
        if len(self) < self.__max_size:
            self.append(frame)
            return
        raise StackOverflowError()

    def is_empty(self):
        return not len(self)

    def top(self, i=0):
        return self[-(i + 1)]


class Thread:

    def __init__(self):
        self.__stack = JvmStack(1024)
        self.pc = 0

    def push_frame(self, frame):
        self.__stack.push(frame)

    def pop_frame(self) -> ThreadFrame:
        return self.__stack.pop()

    def current_frame(self) -> ThreadFrame:
        return self.__stack.top()

    def new_frame(self, method) -> ThreadFrame:
        return Frame.new(self, method)

    def is_not_empty_frame(self) -> bool:
        return not self.__stack.is_empty()

    def clear_stack(self):
        self.__stack.clear()

    def top_frame(self, i=0) -> ThreadFrame:
        return self.__stack.top(i)

    def get_frames(self):
        return list(reversed(self.__stack))

    def frame_len(self):
        return len(self.__stack)

    def new_shim_frame(self, ops: OperandStack) -> ThreadFrame:
        shim = Frame.new_shim(self, ops)
        self.push_frame(shim)
        return shim


class Frame:

    def __init__(self):
        self.thread: Optional[Thread] = None
        self.method: Optional[Method] = None
        self.local_vars: Optional[Slots] = None
        self.operand_stack: Optional[OperandStack] = None
        self.next_pc: int = 0

    @staticmethod
    def new(thread: Thread, method: Method):
        self = Frame()
        self.thread = thread
        self.method = method
        self.local_vars = Slots(method.max_locals)
        self.operand_stack = OperandStack(method.max_stack)
        self.next_pc = 0
        return self

    @staticmethod
    def new_shim(thread: Thread, ops: OperandStack):
        self = Frame()
        self.thread = thread
        self.method = RETURN_METHOD
        self.operand_stack = ops
        return self

    def set_next_pc(self, pc):
        self.next_pc = pc

    def revert_next_pc(self):
        self.next_pc = self.thread.pc


__all__ = ['Slots', 'OperandStack', 'Thread', 'Frame']
